#include "stdafx.h"
#include "ObjContainer.h"
#include "Obj.h"
#include "PlayerObj.h"
#include "core/Collision.hpp"
#include "terrain/Terrain.h"
#include "graphics/ParticleSystem.h"

ObjContainer* ObjContainer::instance = nullptr;

ObjContainer* ObjContainer::getInstance() {
	if(!instance)
		instance = new ObjContainer();
	return instance;
}

ObjContainer::ObjContainer() {

}

void ObjContainer::renderForPicking(FBO_RB pickingFBO, const glm::mat4& viewMatrix) {
	pickingFBO.bind();
	glClearBufferfv(GL_COLOR, 0, glm::value_ptr(glm::vec4(0.0f, 0.0f, 0.0f, 0.0f)));
	const float depthClear = 1.0; // clear to max depth 
	glClearBufferfv(GL_DEPTH, 0, &depthClear);

	draw(viewMatrix, true);
	
	pickingFBO.unbind();
}

void ObjContainer::draw(const glm::mat4 & viewMatrix, bool picking) {

	for ( auto it = objList.begin(); it != objList.end(); it++) {
		(*it)->draw(picking); // put rendering info in BoxPrimitive static list to be rendered
	} 
	Game::getInstance()->boxRenderer->drawBatched(viewMatrix); // draw batched 

	Terrain::getInstance()->render(viewMatrix, picking);
}

void ObjContainer::addObj(Obj* obj) {
	addsScheduled.push_back(obj);
}

Obj* ObjContainer::colorPick(FBO_RB pickingFBO, const glm::dvec2& mousePos) {
	
	glm::vec4 pixel = pickingFBO.sample(glm::dvec2(mousePos.x, Game::windowDim.y - mousePos.y));
	return getObjFromColor(pixel);
}

Obj* ObjContainer::getObjFromColor(const glm::vec4& color)
{
	if ( color.x > 1e-6 || color.y > 1e-6 ) // we have clicked the terrain 
		return nullptr;

	for ( auto it = objList.begin(); it != objList.end(); ++it)
	{
		if ( (*it)->hasPickColor(static_cast<unsigned short>(color.z*0xffff+0.5f)) )
			return (*it);
	}
	return nullptr;
}

bool ObjContainer::walkable(const glm::vec2 & pos, float radius) {

	return Terrain::getInstance()->walkable(pos, radius);

	// Todo support other objects blocking terrain 
}

PlayerObj* ObjContainer::getClosestPlayerObj(Player* ignore, glm::vec3 & pos, float radius) {
	float minDist = radius;
	PlayerObj* closestObj = nullptr;

	for (unsigned int i = 0; i < objList.size(); ++i) {
		if (objList[i]->getType() == Obj::type_playerObj ) {
			PlayerObj* playerObj = static_cast<PlayerObj*>(objList[i]);
			if (playerObj->owner != ignore) {
				float dist = glm::length(pos - playerObj->m_pos);
				if (dist < minDist) {
					minDist = dist;
					closestObj = playerObj;
				}
			}
		}
	}
	return closestObj;
}

void ObjContainer::update(float dt) {
	Terrain* terrain = Terrain::getInstance();
	ParticleSystem* particleSystem = Game::getInstance()->particleSystem;

	for ( auto it = objList.begin(); it != objList.end(); it++) {
		(*it)->update(dt);
	}


	std::vector<CollGeom*> collGeomList;
	// go through game objects, get collision info, put in structure, do collision detection 
	for	( unsigned int i = 0; i < objList.size(); ++i) { 
		collGeomList.push_back(objList[i]->getCollGeom());
	}

	for		( unsigned int i = 0; i < collGeomList.size(); ++i) {
		for ( unsigned int j = i+1; j < collGeomList.size(); ++j ) {
			
			CollGeom* geom0 = collGeomList[i];
			CollGeom* geom1 = collGeomList[j];
			if (!geom0 || !geom1) continue;
			if (geom0->intersects(geom1))  {
				objList[i]->handleColl(objList[j]);
			}
		}
	}

	// projectile terrain collision 
	for	( auto it = objList.begin(); it != objList.end(); ++it ) {
		if ((*it)->getType() != Obj::type_cannonBall) {
			continue;
		}

		bool collision = false;
		CannonBall* projectile = static_cast<CannonBall*>(*it);
		glm::vec2 projPos2d(projectile->m_pos.x, projectile->m_pos.z);
		if (projectile->m_pos.y <= terrain->sample(projPos2d)) {
			projectile->handleColl(terrain);

			// 1. for damage, brute force: just search through all obj's find nearbies and damage them
			//		- Consider adding the quadtree / grid or something for spatial subdivision to limit these types of searches 
			//		- Make things damageable by inheriting from some interface 
			//		- Add projectile box collision
		} 
	}

	// add new objs
	if (!addsScheduled.empty()) {
		objList.insert(objList.end(), addsScheduled.begin(), addsScheduled.end());
		addsScheduled.clear();
	}

	// remove dead objs 
	for	( auto it = objList.begin(); it != objList.end(); ) {
		if ((*it)->toBeDeleted) {
			delete (*it);
			it = objList.erase(it);
		} else { 
			it++;
		}
	}
}

Army::Army(){}
Army::~Army(){}

void Army::addObj(PlayerObj* obj)
{
    objList.push_back(obj);
}

PlayerObj* Army::getObj(const glm::vec4& pixel)
{
    if ( pixel.x > 1e-6 || pixel.y > 1e-6 ) // we have clicked the terrain 
        return nullptr;

    for ( auto it = objList.begin(); it != objList.end(); ++it)
    {
        if ( (*it)->hasPickColor(static_cast<unsigned short>(pixel.z*0xffff+0.5f)) )
            return (*it);
    }
    return nullptr;
}

void Army::boxSelect(const glm::dvec4& box, const glm::mat4& viewMatrix, bool shiftHeld) {
	std::vector<PlayerObj*> boxSelection;
	for (auto it = objList.begin(); it != objList.end(); ++it) {
		if ( Collision::pointInRect((*it)->getScreenPos(viewMatrix), box))
			boxSelection.push_back(*it);
	}
	activeCG.addSelection(boxSelection, shiftHeld);
}

void Army::colorSelect(const glm::vec4& pixel, bool shiftHeld) {
	activeCG.addSelection(getObj(pixel), shiftHeld);
}

void Army::move(glm::vec2 & dest) {
	activeCG.move(dest);
}

PlayerObj* Army::getClosesTarget(const glm::vec3 & pos, float radius) {

	float minDist = radius;
	PlayerObj* closestObj = nullptr;

	for (unsigned int i = 0; i < objList.size(); ++i) {
		 float dist = glm::length(pos - objList[i]->m_pos);
		 if (dist < minDist) {
			 minDist = dist;
			 closestObj = objList[i];
		 }
	}
	return closestObj;
}



